﻿using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Toybox.NicoSharp.Live
{
	using Extensions;
	using Message;
	using Logger;

	[Flags]
	public enum AutoDisconnectMode
	{
		None = 0x00,

		Owner = 0x01,
		Official = 0x02,

		All = Owner | Official,
	}

	public partial class LiveMessageClient : Primitives.AbstractMessageClient
	{

		#region [Static]

		public const string CHAT_FORMAT = "<chat thread=\"{0}\" ticket=\"{1}\" vpos=\"{2}\" postkey=\"{3}\" mail=\"{4}\" user_id=\"{5}\" premium=\"{6}\" locale=\"{7}\">{8}</chat>\0";
		protected static string NoBreakSpace = Encoding.UTF8.GetString(new byte[] { 0xE2, 0x80, 0x8C });

		#endregion [Static]


		#region Events

		/// <summary>
		/// Chat受信時に発生
		/// </summary>
		public event EventHandler<Chat> RecvChat;

		/// <summary>
		/// Chat送信の結果受信時に発生
		/// </summary>
		public event EventHandler<ChatResult> RecvChatResult;

		/// <summary>
		/// Thread受信時発生
		/// </summary>
		public event EventHandler<Thread> RecvThread;

		/// <summary>
		/// Chat受信時発生させる
		/// </summary>
		/// <param name="chat"></param>
		protected virtual void OnRecvChat(Chat chat)
		{
			if (RecvChat != null)
				RecvChat(this, chat);
		}

		/// <summary>
		/// ChatResult受信時発生させる
		/// </summary>
		/// <param name="chatResult"></param>
		protected virtual void OnRecvChatResult(ChatResult chatResult)
		{
			if (RecvChatResult != null)
				RecvChatResult(this, chatResult);
		}

		/// <summary>
		/// Thread受信時発生させる
		/// </summary>
		/// <param name="thread"></param>
		protected virtual void OnRecvThread(Thread thread)
		{
			if (RecvThread != null)
				RecvThread(this, thread);
		}
		#endregion Events


		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		public LiveMessageClient()
			: base()
		{
			this.AutoDisconnectMode = AutoDisconnectMode.All;
		}

		#endregion Constructor


		#region Public Members

		/// <summary>
		/// メッセージスレッド
		/// </summary>
		public Message.Thread MessageThread
		{
			get { return this._messageThread; }
			protected set
			{
				if (this._messageThread == value) return;

				this._messageThread = value;

				this.OnPropertyChanged("MessageThread");
			}
		}
		private Message.Thread _messageThread;

		/// <summary>
		/// コメント投稿用情報
		/// </summary>
		public PostUser User
		{
			get { return this._user; }
			protected set
			{
				if (this._user == value) return;

				this._user = value;

				this.OnPropertyChanged("User");
			}
		}
		private PostUser _user;

		/// <summary>
		/// コメント投稿用キー
		/// </summary>
		public PostKey Key
		{
			get { return this._key; }
			protected set
			{
				if (this._key == value) return;

				this._key = value;

				this.OnPropertyChanged("Key");
			}
		}
		private PostKey _key;

		/// <summary>
		/// 自動切断モード
		/// </summary>
		public AutoDisconnectMode AutoDisconnectMode
		{
			get { return this._autoDisconnectMode; }
			set
			{
				if (this._autoDisconnectMode == value) return;

				this._autoDisconnectMode = value;

				this.OnPropertyChanged("AutoDisconnectMode");
			}
		}
		private AutoDisconnectMode _autoDisconnectMode;

		/// <summary>
		/// 開始時間
		/// </summary>
		public int StartTime
		{
			get { return this._startTime; }
			protected set
			{
				if (this._startTime == value) return;

				this._startTime = value;

				this.OnPropertyChanged("StartTime");
			}
		}
		private int _startTime;

		/// <summary>
		/// 経過時間
		/// </summary>
		public int Vpos
		{
			get { return ((DateTime.UtcNow.GetUnixTime() - this.StartTime) * 100); }
		}

		/// <summary>
		/// 投稿制御
		/// </summary>
		public PostLimitter Limitter
		{
			get
			{
				if (this._limitter == null)
					this._limitter = new PostLimitter();

				return this._limitter;
			}
		}
		private PostLimitter _limitter;

		/// <summary>
		/// NoBreakSpaceの自動付加切り替え
		/// </summary>
		public bool IsAutoSwitchingAttachNBS
		{
			get { return this._isAutoSwitchingAttachNBS; }
			set
			{
				if (this._isAutoSwitchingAttachNBS == value) return;

				this._isAutoSwitchingAttachNBS = value;

				this.OnPropertyChanged("IsAutoSwitchingAttachNBS");
			}
		}
		private bool _isAutoSwitchingAttachNBS;

		#endregion Public Members

		#region Protected Members

		/// <summary>
		/// NBSの付加フラグ
		/// </summary>
		protected bool IsAttachNBS { get; set; }

		/// <summary>
		/// NBS付加用乱数
		/// </summary>
		protected Random Random
		{
			get
			{
				if (this._random == null)
					this._random = new Random();

				return this._random;
			}
		}
		private Random _random;

		#endregion Protected Members


		#region Public Methods

		/// <summary>
		/// Start (Thread)
		/// </summary>
		/// <param name="ps"></param>
		/// <param name="commentCount"></param>
		public void ThreadStart(PlayerStatus ps, int commentCount = 0)
		{
			//Console.WriteLine("Comment Start In " + System.Threading.Thread.CurrentThread.ManagedThreadId);

			this.ThreadStop();

			this.InnerThread = new System.Threading.Thread(new ThreadStart(() =>
			{
				//Console.WriteLine("New Thread " + System.Threading.Thread.CurrentThread.ManagedThreadId);
				this.Start(ps, commentCount);
			}));

			this.InnerThread.Start();
		}

		/// <summary>
		/// Start (Task)
		/// </summary>
		/// <param name="ps"></param>
		/// <param name="commentCount"></param>
		public void TaskStart(PlayerStatus ps, int commentCount = 0)
		{
			this.TaskStop();

			this.InnerTask = Task.Factory.StartNew(() =>
			{
				this.Start(ps, commentCount);
			});
		}

		/// <summary>
		/// Start
		/// </summary>
		/// <param name="ps"></param>
		/// <param name="commentCount"></param>
		public void Start(PlayerStatus ps, int commentCount = 0)
		{
			base.State = ConnectionState.Connecting;

			// 情報格納
			//PInfo = new PostInfo(ps.stream.base_time, ps.Cookies);
			this.User = new PostUser(ps);
			this.StartTime = ps.stream.start_time;

			this.OnConnecting(EventArgs.Empty);

			base.Connect(ps.ms.addr, ps.ms.port, ps.ms.thread, commentCount);
		}

		/// <summary>
		/// コメント送信 (NBSをランダム挿入)
		/// </summary>
		/// <param name="mail"></param>
		/// <param name="body"></param>
		/// <param name="count"></param>
		/// <returns></returns>
		public bool PostUserChat(string mail, string body, int count)
		{
			if (!PostSafetyCheck(body)) return false;

			// メッセージ作成
			body = InsertNoBreakSpace(body, count);

			string data = this.CreateData(mail, body);
			this.WriteData(data);

			return true;
		}

		/// <summary>
		/// コメント送信 (NBSを末尾に挿入)
		/// </summary>
		/// <param name="mail"></param>
		/// <param name="body"></param>
		/// <param name="isAttachNBS"></param>
		/// <returns></returns>
		public bool PostUserChat(string mail, string body, bool isAttachNBS)
		{
			if (!PostSafetyCheck(body)) return false;

			// メッセージ作成
			if (IsAutoSwitchingAttachNBS)
			{
				this.IsAttachNBS = !this.IsAttachNBS;
				isAttachNBS = this.IsAttachNBS;
			}
			if (isAttachNBS)
				body += NoBreakSpace;

			string data = this.CreateData(mail, body);
			this.WriteData(data);

			return true;
		}

		/// <summary>
		/// コメント送信
		/// </summary>
		/// <param name="mail"></param>
		/// <param name="body"></param>
		/// <returns></returns>
		public bool PostUserChat(string mail, string body)
		{
			if (!PostSafetyCheck(body)) return false;

			string data = this.CreateData(mail, body);
			this.WriteData(data);

			return true;
		}

		/// <summary>
		/// コメントデータ作成
		/// </summary>
		/// <param name="mail"></param>
		/// <param name="body"></param>
		/// <returns></returns>
		public string CreateData(string mail, string body)
		{
			if (string.IsNullOrEmpty(body))
				throw new ArgumentNullException("body");

			body = this.ReplaceInvaridChar(body);

			object[] objects ={ 
        this.MessageThread.thread,  //thread
        this.MessageThread.ticket,  //ticket
        this.Vpos,                  //vpos
        this.Key.Value,             //postkey
        mail,                       //mail
        this.User.UserID,           //user_id
        (int)this.User.PremiumType, //user_premium
        this.User.UserDomain,       //userDomain
        body                        //comment
        };

			return string.Format(CHAT_FORMAT, objects);
		}

		/// <summary>
		/// データ送信
		/// </summary>
		/// <param name="data"></param>
		public void WriteData(string data)
		{
			byte[] sendBytes = Encoding.UTF8.GetBytes(data);

			if (this.InnerTcpClient != null && this.InnerTcpClient.Connected)
				this.InnerTcpClient.GetStream().Write(sendBytes, 0, sendBytes.Length);
		}

		#endregion Public Methods

		#region Protected Methods

		/// <summary>
		/// NBS挿入メソッド
		/// </summary>
		/// <param name="comment"></param>
		/// <param name="count"></param>
		/// <returns></returns>
		protected string InsertNoBreakSpace(string comment, int count)
		{
			for (; count > 0; count--)
				comment = comment.Insert(this.Random.Next(comment.Length), NoBreakSpace);

			//for (; count > 0; count--)
			//  comment += _noBreakSpace;

			return comment;
		}

		protected bool PostSafetyCheck(string body)
		{
			// 接続中
			if (this.InnerTcpClient == null || !this.InnerTcpClient.Connected) return false;

			// コメントが空
			if (string.IsNullOrEmpty(body)) return false;

			// Keyの有効
			if (!this.Key.Expired)
			{
				this.Key.Update((MessageThread.last_res + 1) / 100);
			}

			// Limitter
			if (this.Limitter.Enable)
			{
				if (!this.Limitter.Add()) return false;
			}

			return true;
		}

		#region Override Methods

		protected override void DeserializeMessage(string message)
		{
			#region chat

			if (message.StartsWith("<chat "))
			{
				Chat chat = Chat.Deserialize(message);

				// 鍵再取得
				this.MessageThread.last_res = chat.no;
				//PInfo.RenewPostKey(MessageThread.thread, MessageThread.last_res);

				this.OnRecvChat(chat);

				if (chat.comment == "/disconnect")
				{
					if (this.AutoDisconnectMode.HasFlag(AutoDisconnectMode.Owner))
					{
						if (chat.premium == PremiumType.Owner)
							this.Stop();
					}

					if (this.AutoDisconnectMode.HasFlag(AutoDisconnectMode.Official))
					{
						if (chat.premium == PremiumType.System)
							this.Stop();
					}
				}

				return;
			}

			#endregion

			#region chatResult

			if (message.StartsWith("<chat_result "))
			{
				ChatResult result = ChatResult.Deserialize(message);

				if (result.status == ChatState.PostKeyError)
				{
					this.MessageThread.last_res = result.no;
					//PInfo.keyNo = (CThread.last_res + 1) / 100;
					//PInfo.postkey = GetPostKey(CThread.thread, PInfo.keyNo);
					//PInfo.RenewPostKey(MessageThread.thread, MessageThread.last_res, true);
				}

				this.OnRecvChatResult(result);
				return;
			}

			#endregion

			#region thread

			if (message.StartsWith("<thread "))
			{
				this.MessageThread = Message.Thread.Deserialize(message);
				//PInfo.keyNo = (CThread.last_res + 1) / 100;
				//PInfo.postkey = GetPostKey(CThread.thread, PInfo.keyNo);
				//PInfo.RenewPostKey(MessageThread.thread, MessageThread.last_res, true);

				this.OnRecvThread(MessageThread);
				return;
			}

			#endregion

			#region other

			TraceLogger.WriteLine("Unknown Type");

			#endregion
		}

		/// <summary>
		/// 無効な文字を置き換える  (HTMLエンコードの一部)
		/// </summary>
		/// <param name="comment"></param>
		/// <returns></returns>
		protected virtual string ReplaceInvaridChar(string comment)
		{
			comment = comment.Replace("&", "&amp;");
			comment = comment.Replace("<", "&lt;");
			comment = comment.Replace(">", "&gt;");

			return comment;
		}

		#endregion Override Methods

		#endregion Protected Methods

	}
}